Skip to content

feat: client.agent() default-agent support across all SDKs#19

Merged
pmbrull merged 36 commits into
mainfrom
pmbrull/default-agent
Apr 30, 2026
Merged

feat: client.agent() default-agent support across all SDKs#19
pmbrull merged 36 commits into
mainfrom
pmbrull/default-agent

Conversation

@pmbrull
Copy link
Copy Markdown
Member

@pmbrull pmbrull commented Apr 30, 2026

Summary

Adds default-agent support across all 5 SDKs: client.agent() (no name) now invokes the platform's default chat agent (PLANNER / CHAT_MODE), mirroring the chat UI's flow. client.agent("Name") for dynamic agents is unchanged.

Pairs with backend PR open-metadata/openmetadata-collate#3936 (which adds the new POST /v1/agents/invoke endpoint and extracts the shared SSE collector).

What changed

  • Python: AISdk.agent(name=None) returns a DefaultAgentHandle that auto-creates a chat conversation and invokes /v1/agents/invoke (sync) or /v1/agents/run (SSE). 4 unit tests (sync, sync-with-conversation-id, streaming, async). Schema regenerated for PLANNER / INVENTORY.
  • TypeScript: client.agent() overload returns a DefaultAgentHandle. 2 unit tests.
  • Java: client.agent() no-arg overload returns a DefaultAgentHandle. 5 Mockito tests.
  • Rust CLI: --default / -D flag on Invoke and Chat subcommands. 4 wiremock tests.
  • n8n: "Use Default Agent" boolean toggle on the AISdkAgent node. SDK dep bumped to ^0.1.2.
  • Docs: top-level CLAUDE.md + 5 per-SDK READMEs updated with the new example.
  • Spec + plan: committed under docs/superpowers/.

API examples

# Python
client.agent().call("message")              # default
client.agent("MyAgent").call("message")     # named (unchanged)
// TypeScript
await client.agent().invoke('message');
await client.agent('MyAgent').invoke('message');
// Java
client.agent().invoke("message");
client.agent("MyAgent").invoke("message");
# CLI
ai-sdk invoke --default "message"
ai-sdk invoke MyAgent "message"

Coordination

This PR depends on the backend POST /v1/agents/invoke endpoint shipping in open-metadata/openmetadata-collate#3936 — the new sync default-agent path will 404 until that lands.

The TypeScript SDK must be published at 0.1.2 (or newer) before the n8n node can install cleanly from npm; the local symlink works for development. The n8n package.json already pins ^0.1.2 so a fresh install will refuse the older SDK that lacks the no-arg overload.

Test plan

  • cd python && pytest --ignore=tests/integration — all green (180+ tests)
  • cd typescript && npm test — all green (40 tests)
  • cd java && mvn test — all green (49 tests)
  • cd cli && cargo test — all green
  • cd n8n-nodes-metadata && npm run build && npm run lint — clean (requires npm-linked TS SDK 0.1.2)
  • Once backend PR lands: smoke-test client.agent().call(...) against a real instance via make test-integration

Follow-ups (from final code review, non-blocking)

  • Add Java HTTP-level tests (current Java tests mock AISdkHttpClient directly; other SDKs cover wire format end-to-end)
  • Add a vitest unit test for the n8n useDefaultAgent toggle
  • Optionally squash wip(python): ... + the next feat commit before merging for cleaner history
  • Consider switching Python's DefaultAgentHandle from extends AgentHandle to composition (mildly fragile bypass of parent __init__)

🤖 Generated with Claude Code

pmbrull and others added 30 commits April 30, 2026 12:37
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces DefaultAgentHandle class that auto-creates a chat conversation
(POST /api/v1/assistants/chatConversations) when no conversation_id is
supplied, then calls /v1/agents/invoke with agentType=PLANNER and
agentMode=CHAT_MODE, mirroring the chat UI flow.

Also adds test_default_agent.py with two tests that currently fail because
client.agent() still requires a name argument — wiring is deferred to Task 6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors the chat UI flow: auto-creates a chat conversation via
POST /api/v1/assistants/chatConversations, then calls the new
/v1/agents/invoke (sync) or /v1/agents/run (SSE) endpoints with
agentType=PLANNER, agentMode=CHAT_MODE.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the Python SDK and the chat UI flow: auto-creates a chat
conversation via POST /api/v1/assistants/chatConversations, then calls
/v1/agents/invoke (sync) or /v1/agents/run (SSE) with agentType=PLANNER,
agentMode=CHAT_MODE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the Python and TypeScript SDKs and the chat UI flow:
auto-creates a chat conversation via POST /api/v1/assistants/chatConversations,
then calls /api/v1/agents/invoke (sync) or /api/v1/agents/run (SSE) with
agentType=PLANNER, agentMode=CHAT_MODE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the SDK pattern: auto-create chat conversation, then call
/v1/agents/invoke (sync) or /v1/agents/run (SSE) with PLANNER/CHAT_MODE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When enabled, the node uses the platform's default agent (PLANNER /
CHAT_MODE) instead of requiring an agent name. Mirrors client.agent()
(no-arg) on the TypeScript SDK.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add examples of the no-arg client.agent() path (PLANNER / CHAT_MODE) to
the top-level CLAUDE.md API Pattern block and all per-SDK READMEs that
contain a quick-start example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Use Default Agent toggle requires client.agent() (no-arg) which
landed in the TS SDK at 0.1.2. Pinning ^0.1.1 would let a fresh
install resolve the older SDK at runtime and break the new path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Backend now separates Sender.SYSTEM (thinking) and Sender.ASSISTANT
(final body) messages in the sync invoke flow, exposing them as a new
thinkingSteps field on InvokeAgentResponse. Surfaced as
thinking_steps (Python, Rust) / thinkingSteps (TS, Java, n8n) on the
SDK's InvokeResponse model. Backward-compatible: defaults to [] when
the backend doesn't supply the field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty stubs for AgentsAPI, BotsAPI, PersonasAPI, AbilitiesAPI, and
MemoriesAPI. Methods land in subsequent commits. __init__.py is a
docstring-only module — callers import from canonical paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…odels

Adds memory models to ai_sdk.models:
- MemoryType, MemoryScope, MemoryVisibility enums
- CreateContextMemoryRequest with to_api_dict() that flattens
  visibility -> shareConfig.visibility on the wire
- ContextMemory.from_dict() that flattens shareConfig.visibility
  back to a top-level visibility field for ergonomics
- MemorySearchHit / MemorySearchResults parsed from OpenSearch shape

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds DELETE method to HTTPClient (sync + async) for soft/hard delete.
Implements MemoriesAPI with sync and async variants of every operation.
Search uses the NLQ hybrid endpoint at /api/v1/hybrid/nlq/search with
filters serialized as JSON query params.

The MemoryList type alias works around the type checker conflating the
list method with the builtin list type.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…classes

AgentsAPI, BotsAPI, PersonasAPI, AbilitiesAPI each holds its own HTTP
clients and exposes list/get/create/etc. AgentsAPI takes a back-reference
to AISdk so create() can resolve persona and ability names via the new
namespaces.

Pagination helper is duplicated per file to keep each namespace
self-contained — extract if a fifth namespace appears.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rewrites AISdk to expose:
- client.agents (list, create) + async variants
- client.bots (list, get) + async variants
- client.personas (list, get, create) + async variants
- client.abilities (list, get) + async variants
- client.memories (list, get, create, delete, search) + async variants

Removes: client.list_agents/create_agent, client.list_bots/get_bot,
client.list_personas/get_persona/create_persona, client.list_abilities/
get_ability, plus all aXxx async variants.

client.agent(name) is unchanged — it returns an AgentHandle.
client.mcp is unchanged.

Updates langchain integration code and all unit tests to use the new
namespace API. Modernizes 3 persona async-error tests from the
deprecated asyncio.get_event_loop() pattern to asyncio.run().

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Adds memory models to package-level __all__ and __init__.py
  re-exports so users can `from ai_sdk import ContextMemory, ...`
- Adds integration test that round-trips create -> get -> list ->
  search -> delete against a real instance (gated on AI_SDK_HOST
  and AI_SDK_TOKEN)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Adds CHANGELOG.md with the breaking-change migration table for all
  four SDKs (Python, TypeScript, Java, Rust CLI) and the new
  client.memories namespace.
- Updates CLAUDE.md API Pattern section with namespace examples.
- Updates docs/quickstart.md, docs/async.md, docs/standalone.md to use
  client.<entity>.<verb>() form.
- Updates cookbook agent-config docs (gdpr-dsar-compliance, dbt-pr-review,
  dq-failure-slack-notifications).
- Updates n8n integration test to use client.agents.list().

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds list/get/create/delete/search subcommands under `ai-sdk memories`,
mirroring the Phase 1 Python MemoriesAPI surface. Hits
/api/v1/contextCenter/memories for CRUD and /api/v1/hybrid/nlq/search for
hybrid NLQ search over the contextMemory index.

The wire format follows the platform schema: memoryType/memoryScope as
explicit enum strings, visibility nested under shareConfig on requests
and flattened back via custom Deserialize on responses, and tag FQNs
wrapped in the TagLabel envelope (Manual/Confirmed/Classification).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the model layer for the Java memories namespace:

- MemoryType, MemoryScope, MemoryVisibility enums (with @jsonvalue
  serialization to camelCase wire values like "Preference",
  "EntityScoped", "Private")
- CreateContextMemoryRequest with builder + toApiMap() that re-nests
  visibility under shareConfig and wraps tags as TagLabels
  (labelType=Manual, state=Confirmed, source=Classification)
- ContextMemory POJO with a static fromMap() factory that flattens
  shareConfig.visibility back to a top-level visibility field
- MemorySearchHit + MemorySearchResults with MemorySearchResults
  fromOpenSearch() parser for the OpenSearch hits.total.value /
  hits.hits[i]._source/_score response shape

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces an api/ package with one namespace class per entity domain:

- AgentsApi: list / create (resolves persona + ability names via the
  parent AISdk back-reference)
- BotsApi:  list / get
- PersonasApi: list / get / create
- AbilitiesApi: list / get
- MemoriesApi: list (paginated) / get / create / delete / search
  (hybrid NLQ search via /api/v1/hybrid/nlq/search), backed by two
  AISdkHttpClient instances — one rooted at /api/v1/contextCenter/
  memories for CRUD and one rooted at /api/v1 for search

Adds the supporting low-level helpers on AISdkHttpClient:

- A new constructor that accepts a base path so additional clients
  can target the memories and search endpoints
- Generic getMap / postMap / delete (path, params) methods used by
  the namespace classes for arbitrary JSON requests against any
  base URL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Converts AISdk's per-entity flat methods to namespaced accessors that
return the new api/ package classes, matching the Python and
TypeScript SDK shape.

- AISdk now constructs three AISdkHttpClient instances (default,
  memories CRUD, hybrid search) and exposes agents() / bots() /
  personas() / abilities() / memories() accessors
- Removes flat methods listAgents / createAgent / listBots / getBot /
  listPersonas / getPersona / createPersona / listAbilities /
  getAbility — callers must use client.agents().list() etc.
- agent(name) factory and the no-arg agent() default-agent factory
  are unchanged
- AISdkTest and IntegrationTest are updated to the namespace form
- Adds MemoriesApiTest covering list pagination, primaryEntityFqn
  filtering, limit, get, create (toApiMap shape including tag
  wrapping and shareConfig nesting), delete (soft + hard), search
  parameters, search filter JSON serialization, OpenSearch response
  parsing, and ContextMemory.fromMap visibility flattening

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the memory data model surface for the upcoming MemoriesApi namespace:
MemoryType / MemoryScope / MemoryVisibility string-literal unions,
ContextMemory, MemorySearchHit, MemorySearchResults, and
CreateContextMemoryRequest. Mirrors the Pydantic models in the Python
SDK and is purely additive — existing exports are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces a new src/api/ directory with one namespace class per
entity domain:

- AgentsApi: list / create (resolves persona + ability names via the
  parent AISdk back-reference)
- BotsApi: list / get
- PersonasApi: list / get / create
- AbilitiesApi: list / get
- MemoriesApi: list (paginated, optionally filtered by primaryEntityFqn)
  / get / create / delete (soft + hard) / search (hybrid NLQ via
  /api/v1/hybrid/nlq/search), backed by two HttpClient instances —
  one rooted at /api/v1/contextCenter/memories for CRUD and one at
  /api/v1 for the search endpoint.

Adds the supporting helpers on HttpClient: a generic delete() method,
boolean query-parameter values (for hardDelete), entityType /
entityName on public verb methods so namespaces can surface typed
404 errors, and a parseBody() helper that returns void for empty
responses.

The existing flat methods on AISdk remain in place for now; the next
commit replaces them with namespace accessors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…sors

Drops the listAgents / createAgent / listBots / getBot / listPersonas /
getPersona / createPersona / listAbilities / getAbility surface from
AISdk and replaces it with composed namespace fields:

  client.agents.list() / client.agents.create(req)
  client.bots.list()   / client.bots.get(name)
  client.personas.list() / client.personas.get(name) / client.personas.create(req)
  client.abilities.list() / client.abilities.get(name)
  client.memories.list() / .get / .create / .delete / .search

The agent handle factory (client.agent(name)) is unchanged.

AISdk now constructs one HttpClient per entity rooted at its API
path; the namespace classes issue plain relative requests against
that base URL. The legacy getAbsolute / postAbsolute helpers on
HttpClient are removed since no caller needs them anymore.

Tests: client.test.ts and integration.test.ts mechanically renamed
flat-method calls to namespace form. README.md updated to show the
namespace API and adds a Context Memories section.

This is a breaking change at v0.1.x (documented in CHANGELOG.md /
spec).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brainstorming/plan artifacts live alongside the code locally but are
not part of the merged history. Mirrors the existing docs/plans/**
ignore pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a new "Context Memories" section to:

- README.md (root) — concise pitch + Python/TS/Java/CLI examples for
  the create -> list -> search -> delete cycle, including filter usage.
- python/README.md — full examples (sync + async), enum cheatsheet,
  stored-fields table.
- java/README.md — namespace examples for all entities (replaces the
  old flat method calls that were missed in the Phase 3 refactor) plus
  a Memories section with builder usage and the stored-fields table.
- cli/README.md — `ai-sdk memories` subcommand reference with all
  flags, enum values, and a --json example.

The TypeScript README already had a Memories section from Phase 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a synthetic "AskCollate (default)" entry as the first item in the
chat agent selection menu. Selecting it routes the chat to the
platform's default agent (use_default = true) instead of a named
dynamic agent. The menu is now useful even when there are no
API-enabled named agents.

Behaviour:
- Menu always has AskCollate at the top with description
  "The platform's default agent — no specific agent selected"
- Selecting AskCollate sets use_default=true and labels the header
  "AskCollate"
- Selecting a named agent sets use_default=false (clears the flag if
  the chat was previously in default mode)

Adds 4 unit tests covering the prepend, the empty-list case, and both
selection paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pmbrull and others added 4 commits April 30, 2026 13:00
Each thinking event from the stream is a discrete reasoning step.
Previously they were concatenated without separators, so they all
collapsed onto a single rendered line — hard to read.

- TUI: append_thinking_content now inserts a newline between
  non-empty steps when the buffer doesn't already end in one. The
  rendering path uses .lines() so each step shows on its own row.
- invoke --thinking: print each step with println!() (after trimming
  trailing whitespace) so each lands on its own line.

Adds 2 unit tests covering the separator-injection and the case where
a step already ended in a newline (no extra blank line).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related TUI improvements:

1. Markdown links: previously the renderer fell into the catch-all
   _ => {} arm for Tag::Link / TagEnd::Link, so the visible label
   rendered with no styling and the URL was dropped. Now the label
   gets a blue + underlined style, and " (url)" is appended in
   dimmed text afterwards. Nested links are supported via a URL
   stack.

2. Thinking steps: the live-streaming and persisted thinking blocks
   were rendered as raw lines, so any markdown the agent emitted
   (links, bold, code) leaked through unstyled. They now go through
   a new render_markdown_dimmed() that runs the full markdown
   pipeline and then collapses every foreground colour to DarkGray
   while preserving bold/italic/underline modifiers. Each rendered
   line is indented two spaces under the "Thinking:" header.

Adds 3 unit tests: link label + url presence, link underline modifier,
and dimmed-renderer colour override + modifier preservation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The audit found two stale spots in docs and examples:

1. docs/API_REFERENCE.md — every per-SDK method table still showed
   the old flat methods (client.list_agents, client.listAgents,
   client.listAgents() in Java) and was missing the new memories
   namespace entirely. The Server Endpoint Map missed the memory
   CRUD and hybrid search endpoints. Data Models section had no
   ContextMemory / CreateContextMemoryRequest / MemorySearchResults /
   EntityReference. Feature Parity Matrix missed the Memories row
   and the default-agent feature.

2. cookbook/{gdpr-dsar-compliance,dbt-pr-review,dq-failure-slack-
   notifications}/agent-config.md — the TypeScript and Java code
   blocks still called client.createAgent(...). Updated to
   client.agents.create(...) for TS and client.agents().create(...)
   for Java.

Verified: a repo-wide grep for flat-method patterns now returns
zero hits outside the CHANGELOG migration table and gitignored
local design docs. All linters pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Long agent runs (>2 min) were being cut mid-stream because each SDK's
HTTP client applied its standard request timeout to SSE response bodies.
The failure surfaces as a decode/connection error, masking the timeout
as the real cause.

- Rust CLI: separate streaming reqwest client with no timeout
- Python: separate httpx client with read=None for post_stream (sync + async)
- TypeScript: dropped AbortController deadline from postStream
- Java: only connectTimeout was set; added Javadoc warnings to lock that in

Default timeout for non-streaming calls bumped 120 -> 900s across all four.

Also adds 'ai-sdk chat --debug [PATH]' to the Rust CLI: writes SSE event
traces (each event, message, and the underlying error + unparsed buffer
when a stream dies) to a file. Defaults to ~/.ai-sdk/chat-debug.log.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pmbrull pmbrull force-pushed the pmbrull/default-agent branch from 49d9a45 to a7df37e Compare April 30, 2026 15:54
pmbrull and others added 2 commits April 30, 2026 18:13
Three independent CI failures caused by tool versions newer than what
local dev had cached:

- Rust: clippy 1.95 expanded `collapsible_match` to flag arms whose body
  is a single conditional. Convert 6 such arms in `cli/src/tui/*` to use
  match guards.
- Python: ty 0.0.33 only recognizes the alias name from `Field(alias=...)`
  as a valid kwarg, ignoring `populate_by_name`/`validate_by_name`.
  Replace explicit per-field aliases with `alias_generator=to_camel` on
  the model_config; ty then accepts snake_case kwargs while pydantic
  still produces/parses camelCase via the auto-generated aliases.
- n8n: `npm install` was pulling `@openmetadata/ai-sdk` from the npm
  registry, which lacks `thinkingSteps`. Switch the dep to
  `file:../typescript` so CI builds against the local SDK (matches what
  the local dev symlink already does).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Minor bump: SSE timeout handling changed across all SDKs (separate
streaming client / no request deadline on streams), default request
timeout 120s -> 900s, and the Rust CLI gained `chat --debug [PATH]`
for SSE event tracing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pmbrull pmbrull merged commit 7aa0881 into main Apr 30, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant